iT邦幫忙

2025 iThome 鐵人賽

DAY 19
3
Software Development

PostgreSQL 效能優化 30 天挑戰:從 Index 到 Transaction 的深入探索系列 第 19

Day 19 - Isolation Levels:交易同時進行時,怎麼避免資料錯亂?

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250902/20177885eibL5Nkr1M.png

在高併發(High Concurrency)的場景下,我們除了關心速度之外,更重要的是「資料會不會錯」。想像你正在操作一筆銀行轉帳交易,當你從帳戶扣款的同時,有另一個交易在查詢餘額,結果查到了錯誤的金額;或者你剛查完商品數量,下一行要下訂單時,庫存卻已經變了,這些問題,都是交易隔離層級(Isolation Levels)要處理的範圍。

在介紹解法之前,我們先來看看不同隔離層級時,會遇到哪些問題:

Dirty read

Dirty read 是指在 transaction 內,讀到了另一個 transaction 還沒被 commit 的資料。因為另一個 transaction 可能之後會被 rollback,就會造成誤判。
https://ithelp.ithome.com.tw/upload/images/20250814/20177885E1Fte3DILH.png

Non-repeatable Reads

Non-repeatable Reads 是指同一筆資料,在同一個 transaction 中讀兩次,卻得到不同的結果。跟 Dirty read 的差別是這時候 connection 2 是有 commit 的狀態。

https://ithelp.ithome.com.tw/upload/images/20250814/20177885DyFegAzcJv.png

Phantom Reads

Phantom Reads 是指在同一個 transction 中,查詢一整筆資料後,因為其他 transaction 塞入了新資料,導致再次查詢時出現資料筆數不一致的情況。

https://ithelp.ithome.com.tw/upload/images/20250814/20177885dzP80Qc05a.png

Serialization Anomaly

Serialization Anomaly 是指多筆 transaction 結果無法用單一順序解釋的邏輯矛盾。這句話聽起很抽象,可以直接用例子來看會比較容易懂。

假設初始資料如下:

Category Amount
1 10
1 20
2 100
2 200

Connection 1: Category 1 的總和為 30,我想以這個總和幫 Category 2 加一筆新的資料
Connection 2: Category 2 的總和為 300,我想以這個總和幫 Category 1 加一筆新的資料

https://ithelp.ithome.com.tw/upload/images/20250814/20177885HpLG7DLTuy.png

假設兩者同時發生,Category 1 會新增 300,Category 2 會新增 30。但是如果是有順序性的話:

A. Connection 1 先執行,接著換成 Connection 2

Connection 1: Category 1 的總和為 30,我想以這個總和幫 Category 2 加一筆新的資料
Connection 2: Category 2 的總和為 330,我想以這個總和幫 Category 1 加一筆新的資料

https://ithelp.ithome.com.tw/upload/images/20250814/20177885mvFiicyTet.png

Category 2 會先新增 30,Category 1 會基於前一步而變成新增 330。

B. Connection 2 先執行,接著換成 Connection 1

Connection 2: Category 2 的總和為 300,我想以這個總和幫 Category 1 加一筆新的資料
Connection 1: Category 1 的總和為 330,我想以這個總和幫 Category 2 加一筆新的資料

https://ithelp.ithome.com.tw/upload/images/20250814/20177885P3Zz144MMe.png

Category 1 會先新增 300,Category 2 會基於前一步而變成新增 330。

結果就會發現,不論是情境 A 還是情境 B,好像都沒辦法還原一開始的情境。當兩個 request 同時發生時,實際上兩者都會查到了原本的 30 和 300,這種情況就是 Serialization Anomaly。

Isolation levels

我們了解了在同時併發可能會造成的錯誤以後,就可以來認識 Isolation Levels 了。在 PostgreSQL 當中,設定不同的 Isolation Level,可以避免掉不同的異常狀況。可以參考官方的表格:

https://ithelp.ithome.com.tw/upload/images/20250814/201778853z4MEI7hHi.png

https://www.postgresql.org/docs/current/transaction-iso.html

Read uncommitted

在 Read uncommitted 中,四種狀況都有可能會發生,不過 PG 的設計是 Read uncommitted 和 Read committed 的行為一樣,所以可以看到左上角是寫 Allowed, but no in PG。

Read committed

Read committed 可以解決的是 Dirty read,不過其他三種狀況還可能會發生。而 Read committed 也是 PostgreSQL 預設的隔離層級。

Repeatable read

Repeatable read 可以解決 Dirty read & Non repeatable read,剩下兩種錯誤狀況會發生。PostgreSQL 的設計是不會發生 Phantom read。

Serializable

最後一個層級 Serializable 就可以避免四種錯誤情況發生,看到這裡可能會有個疑問,那為何不把隔離層級都設為 Serializable?這樣資料一定都沒問題啦~

那我們就來看一下 Serializable 他是怎麼做到這件事的,來看看官方文件怎麼說:

The Serializable isolation level provides the strictest transaction isolation. This level emulates serial transaction execution for all committed transactions; as if transactions had been executed one after another, serially, rather than concurrently.

在 Serializable 的層級下,它模擬所有交易是「是依序、一筆一筆執行」的效果。它的運作方式其實和 Repeatable Read 很像,但多了「監控機制」,用來偵測可能導致非序列化結果的狀況(也就是 Serialization Anomaly)。

所以當系統發現這種衝突時,會回報錯誤並 rollback 其中一個交易。

也就是因為他會模擬交易是依序的,需要額外多了監控機制,在高併發、大流量場景的時候,應該可以想像因為 transaction 彼此依賴、互相影響的機會多了,衝突就多,也更容易 rollback。所以事實上 Serializable 確保交易時序以及資料穩定性,但會犧牲花費時間。

明天我們就來實驗看看,在不同隔離層級之下,觀察是否真的會出現這些錯誤現象,那就明天見了~

重點回顧

  • Dirty Read:讀到尚未 commit 的資料,可能造成誤判。
  • Non-repeatable Read:同一筆資料在同一 transaction 中讀兩次結果不同。
  • Phantom Read:查詢條件相同但結果筆數不同,因為有新資料寫入。
  • Serialization Anomaly:兩個 transaction 各自根據彼此原始狀態做操作,最終結果出現邏輯矛盾。
  • 除了關心速度之外,更重要的是理解不同隔離層級的特性,根據需求選擇適當的隔離層級,來在「效能」與「一致性」之間取得平衡

參考資料

https://www.postgresql.org/docs/current/transaction-iso.html


上一篇
Day 18 - CREATE INDEX CONCURRENTLY:不影響業務的情況下建立 Index
下一篇
Day 20 - 觀察 PostgreSQL 隔離層級:Read Committed、Repeatable Read
系列文
PostgreSQL 效能優化 30 天挑戰:從 Index 到 Transaction 的深入探索20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言